// let gameLoopId;

class EventEmitter {
    constructor() {
        this.listeners = {};
    }

    on(message, listener) {
        if (!this.listeners[message]) {
            this.listeners[message] = [];
        }
        this.listeners[message].push(listener);
    }

    emit(message, payload = null) {
        if (this.listeners[message]) {
            this.listeners[message].forEach((l) => l(message, payload));
        }
    }
    clear() {
        this.listeners = {};
    }
}

class Game {
    constructor() {
        this.points = 0;
        this.life = 3;
        this.end = false;
        this.ready = false;

        eventEmitter.on(Messages.MONSTER_OUT_OF_BOUNDS, () => {
            hero.dead = true;
        });

        eventEmitter.on(Messages.HERO_SPEED_LEFT, () => {
            hero.speed.x = -10;
            hero.img = heroImgLeft;
        });

        eventEmitter.on(Messages.HERO_SPEED_RIGHT, () => {
            hero.speed.x = 10;
            hero.img = heroImgRight;
        });

        eventEmitter.on(Messages.HERO_SPEED_ZERO, () => {
            hero.speed = { x: 0, y: 0 };
            if (game.life === 3) {
                hero.img = heroImg;
            } else {
                hero.img = heroImgDamaged;
            }
        });

        eventEmitter.on(Messages.HERO_FIRE, () => {
            if (coolDown === 0) {
                let l = new Laser(hero.x + 45, hero.y - 30);
                l.img = laserRedImg;
                gameObjects.push(l);
                cooling();
            }
        });

        eventEmitter.on(Messages.GAME_END_LOSS, (_, gameLoopId) => {
            game.end = true;
            displayMessage('You died... - Press [Enter] to start the game Captain Pew Pew');
            clearInterval(gameLoopId);
        });

        eventEmitter.on(Messages.GAME_END_WIN, (_, gameLoopId) => {
            game.end = true;
            displayMessage('Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew', 'green');
            clearInterval(gameLoopId);
        });

        eventEmitter.on(Messages.COLLISION_MONSTER_LASER, (_, { first: laserImg, second: monster }) => {
            laser.dead = true;
            monster.dead = true;
            this.points += 100;

            gameObjects.push(new Explosion(monster.x, monster.y, laserRedShot));
        });

        eventEmitter.on(Messages.COLLISION_MONSTER_HERO, (_, { monster: m, id }) => {
            game.life--;
            if (game.life === 0) {
                hero.dead = true;
                eventEmitter.emit(Messages.GAME_END_LOSS, id);
                gameObjects.push(new Explosion(hero.x, hero.y, laserGreenShot));
            }
            hero.img = heroImgDamaged;
            m.dead = true;
            gameObjects.push(new Explosion(m.x, m.y, laserRedShot));
        });
        
        eventEmitter.on(Messages.KEY_EVENT_UP, () => {
			hero.y = hero.y > 0 ? hero.y - 5 : hero.y;
		});
		eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
			hero.y = hero.y < HEIGHT ? hero.y + 5 : hero.y;
		});
		eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
			hero.x = hero.x > 0 ? hero.x - 10 : hero.x;
		});
		eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
			hero.x = hero.x < WIDTH ? hero.x + 10 : hero.x;
		});
		eventEmitter.on(Messages.GAME_START, () => {
			if (game.ready && game.end) {
				// assets loaded
				runGame();
			}
		});
    }
}

class GameObject {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.dead = false;
        this.type = '';
        this.width = 0;
        this.height = 0;
        this.img = undefined;
    }

    draw(ctx) {
        ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
    }

    rectFromGameObject() {
        return {
            top: this.y,
            left: this.x,
            bottom: this.y + this.height,
            right: this.x + this.width,
        };
    }
}

function draw(ctx, objects) {
    objects.forEach((obj) => {
        obj.draw(ctx);
    });
}

function rectFromGameObject(go) {
    return {
        top: go.y,
        left: go.x,
        bottom: go.y + go.height,
        right: go.x + go.width,
    };
}

class Hero extends GameObject {
    constructor(x, y) {
        super(x, y);
        (this.width = 99), (this.height = 75);
        this.type = 'Hero';
        this.speed = { x: 0, y: 0 };
        // this.cooldown = 0;
        // this.life = 3;
        // this.points = 0;
    }
    // fire() {
    //     gameObjects.push(new Laser(this.x + 45, this.y - 10));
    //     this.cooldown = 500;

    //     let id = setInterval(() => {
    //         if (this.cooldown > 0) {
    //             this.cooldown -= 100;
    //             if (this.cooldown === 0) {
    //                 clearInterval(id);
    //             }
    //         }
    //     }, 200);
    // }
    // canFire() {
    //     return this.cooldown === 0;
    // }
    // decrementLife() {
    //     this.life--;
    //     if (this.life === 0) {
    //         this.dead = true;
    //     }
    // }
    // incrementPoints() {
    //     this.points += 100;
    // }
}

class Enemy extends GameObject {
    constructor(x, y) {
        super(x, y);
        (this.width = 98), (this.height = 50);
        this.type = 'Enemy';
        let id = setInterval(() => {
            if (this.y < canvas.height - this.height) {
                this.y += 5;
            } else {
                console.log('Stopped at ', this.y);
                clearInterval(id);
            }
        }, 300);
    }
}

class Monster extends GameObject {
    constructor(x, y) {
        super(x, y);
        (this.width = 98), (this.height = 50);
        this.type = 'Monster';
        let id = setInterval(() => {
            if (!this.dead) {
                this.y = this.y < HEIGHT ? this.y + 30 : this.y;
                if (this.y >= HEIGHT - this.height) {
                    this.dead = true;
                    eventEmitter.emit('MONSTER_OUT_OF_BOUNDS')
                }
            } else {
                clearInterval(id);
            }
        }, 1500);
    }
}

class Laser extends GameObject {
    constructor(x, y) {
        super(x, y);
        (this.width = 9), (this.height = 33);
        this.type = 'Laser';
        this.img = laserImg;
        let id = setInterval(() => {
            if (!this.dead) {
                this.y = this.y > 0 ? this.y - 20 : this.y;
                if (this.y <= 0) {
                    this.dead = true;
                }
            } else {
                clearInterval(id);
            }
        }, 100);
    }
}

class Explosion extends GameObject {
    constructor(x, y, img) {
        super(x, y);
        this.img = img;
        this.type = 'Explosion';
        (this.width = 56 * 2), (this.height = 54 * 2);
        setTimeout(() => {
            this.dead = true;
        }, 300);
    }
}

class Moster extends GameObject {
    constructor(x, y) {
        super(x, y);
        this.type = 'Moster';
        (this.width = 98), (this.height = 50);
        let id = setInterval(() => {
            if (!this.dead) {
                this.y = this.y < HEIGHT ? this.y + 30 : this.y;
                if (this.y >= HEIGHT - this.height) {
                    this.dead = true;
                    eventEmitter.emit('MONSTER_OUT_OF_BOUNDS');
                }
            } else {
                clearInterval(id);
            }
        }, 1500);
    }
}

function loadTexture(path) {
    return new Promise((resolve) => {
        const img = new Image();
        img.src = path;
        img.onload = () => {
            resolve(img);
        };
    });
}

function intersectRect(r1, r2) {
    return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top);
}

const Messages = {
    MONSTER_OUT_OF_BOUNDS: 'MONSTER_OUT_OF_BOUNDS',
    HERO_SPEED_LEFT: 'HERO_MOVING_LEFT',
    HERO_SPEED_RIGHT: 'HERO_MOVING_RIGHT',
    HERO_SPEED_ZERO: 'HERO_SPEED_ZERO',
    HERO_FIRE: 'HERO_FIRE',
    KEY_EVENT_UP: 'KEY_EVENT_UP',
    KEY_EVENT_DOWN: 'KEY_EVENT_DOWN',
    KEY_EVENT_LEFT: 'KEY_EVENT_LEFT',
    KEY_EVENT_RIGHT: 'KEY_EVENT_RIGHT',
    KEY_EVENT_SPACE: 'KEY_EVENT_SPACE',
    COLLISION_ENEMY_LASER: 'COLLISION_ENEMY_LASER',
    COLLISION_ENEMY_HERO: 'COLLISION_ENEMY_HERO',
    KEY_EVENT_ENTER: 'KEY_EVENT_ENTER',
    GAME_END_LOSS: 'GAME_END_LOSS',
    GAME_END_WIN: 'GAME_END_WIN',
    GAME_START: 'GAME_START',
};

const eventEmitter = new EventEmitter();
const hero = new Hero(0, 0);
const WIDTH = 1024;
const HEIGHT = 768;
let laserRedImg;
let laserRedShot;
let laserGreenShot;
let canvas;
let ctx;
let heroImgDamaged;
let monsterImg;
let heroImgLeft;
let heroImgRight;
let coolDown = 0;
let heroImg,
    enemyImg,
    laserImg,
    lifeImg,
    gameObjects = [];

const game = new Game();

let onKeyDown = function (e) {
    console.log(e.keyCode);
    switch (e.keyCode) {
        case 37:
        case 39:
        case 38:
        case 40:
        case 32:
            e.preventDefault();
            break;
        default:
            break;
    }
};

window.addEventListener('keydown', onKeyDown);
window.addEventListener('keydown', (e) => {
    switch (e.keyCode) {
        case 37:
            eventEmitter.emit(Messages.HERO_SPEED_LEFT);
            break;
        case 39:
            eventEmitter.emit(Messages.HERO_SPEED_RIGHT);
            break;
    }
});

window.addEventListener('keyup', (evt) => {
    eventEmitter.emit(Messages.HERO_SPEED_ZERO);
    if (evt.key === 'ArrowUp') {
        eventEmitter.emit(Messages.KEY_EVENT_UP);
    } else if (evt.key === 'ArrowDown') {
        eventEmitter.emit(Messages.KEY_EVENT_DOWN);
    } else if (evt.key === 'ArrowLeft') {
        eventEmitter.emit(Messages.KEY_EVENT_LEFT);
    } else if (evt.key === 'ArrowRight') {
        eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
    } else if (evt.keyCode === 32) {
        eventEmitter.emit(Messages.HERO_FIRE);
    } else if (evt.key === 'Enter') {
        eventEmitter.emit(Messages.GAME_START)
    }
});

function cooling() {
    coolDown = 500;
    let id = setInterval(() => {
        coolDown -= 100;
        if (coolDown === 0) {
            clearInterval(id);
        }
    }, 100);
}

function createEnemies() {
    const MONSTER_TOTAL = 5;
    const MONSTER_WIDTH = MONSTER_TOTAL * 98;
    const START_X = (canvas.width - MONSTER_WIDTH) / 2;
    const STOP_X = START_X + MONSTER_WIDTH;

    for (let x = START_X; x < STOP_X; x += 98) {
        for (let y = 0; y < 50 * 5; y += 50) {
            const enemy = new Enemy(x, y);
            enemy.img = enemyImg;
            gameObjects.push(enemy);
        }
    }
}

function createMonsters(monsterImg) {
    const MONSTER_TOTAL = 5;
    const MONSTER_WIDTH = MONSTER_TOTAL * 98;
    const START_X = (canvas.width - MONSTER_WIDTH) / 2;
    const STOP_X = START_X + MONSTER_WIDTH;

    for (let x = START_X; x < STOP_X; x += 98) {
        for (let y = 0; y < 50 * 5; y += 50) {
            gameObjects.push(new Monster(x, y));
        }
    }
    gameObjects.forEach((go) => {
        go.img = monsterImg;
    })
}

function createHero() {
    hero = new Hero(canvas.width / 2 - 45, canvas.height - canvas.height / 4);
    hero.img = heroImg;
    gameObjects.push(hero);
}

function createHero(heroImg) {
    hero.dead = false;
    hero.img = heroImg;
    hero.y = (canvas.height / 4) * 3;
    hero.x = canvas.width / 2;
    gameObjects.push(hero);
}

function checkGameState(gameLoopId) {
    const monsters = gameObjects.filter((go) => go.type === 'Monster');
    if (hero.dead) {
        eventEmitter.emit(Messages.GAME_END_LOSS, gameLoopId);
    } else if (monsters.length === 0) {
        eventEmitter.emit(Messages.GAME_END_WIN);
    }

    if (hero.speed.x !== 0) {
        hero.x += hero.speed.x;
    }

    const lasers = gameObjects.filter((go) => go.type === 'Laser');
    lasers.forEach((l) => {
        monsters.forEach((m) => {
            if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
                eventEmitter.emit(Messages.COLLISION_MONSTER_LASER, {
                    first: l,
                    second: m,
                });
            }
        });
    });

    monsters.forEach((m) => {
        if (intersectRect(m.rectFromGameObject(), hero.rectFromGameObject())) {
            eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, {
                monster: m,
                id: gameLoopId
            });
        }
    });

    gameObjects = gameObjects.filter((go) => !go.dead);
}

function updateGameObjects() {
    const enemies = gameObjects.filter((go) => go.type === 'Enemy');
    const lasers = gameObjects.filter((go) => go.type === 'Laser');

    enemies.forEach((enemy) => {
        const heroRect = hero.rectFromGameObject();
        if (intersectRect(heroRect, enemy.rectFromGameObject())) {
            eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, { enemy });
        }
    });

    lasers.forEach((l) => {
        enemies.forEach((m) => {
            if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
                eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
                    first: l,
                    second: m,
                });
            }
        });
    });

    gameObjects = gameObjects.filter((go) => !go.dead);
}

function drawGameObjects(ctx) {
    gameObjects.forEach((go) => go.draw(ctx));
}

function initGame() {
    gameObjects = [];
    createEnemies();
    createHero();

    eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
        resetGame();
    });

    eventEmitter.on(Messages.KEY_EVENT_UP, () => {
        hero.y -= 5;
    });

    eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
        hero.y += 5;
    });

    eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
        hero.x -= 20;
    });

    eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
        hero.x += 20;
    });

    eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
        if (hero.canFire()) {
            hero.fire();
        }
    });

    eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
        first.dead = true;
        second.dead = true;
        hero.incrementPoints();

        if (isEnemiesDead()) {
            eventEmitter.emit(Messages.GAME_END_WIN);
        }
    });

    eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
        enemy.dead = true;
        hero.decrementLife();
        if (isHeroDead()) {
            eventEmitter.emit(Messages.GAME_END_LOSS);
            return;
        }
        if (isEnemiesDead()) {
            eventEmitter.emit(Messages.GAME_END_WIN);
        }
    });

    eventEmitter.on(Messages.GAME_END_WIN, () => {
        endGame(true);
    });

    eventEmitter.on(Messages.GAME_END_LOSS, () => {
        endGame(false);
    });
}
function drawLife() {
    const START_POS = canvas.width - 180;
    for (let i = 0; i < hero.life; i++) {
        ctx.drawImage(lifeImg, START_POS + 45 * (i + 1), canvas.height - 37);
    }
}

function drawPoints() {
    ctx.font = '30px Arial';
    ctx.fillStyle = 'red';
    ctx.textAlign = 'left';
    drawText('Points:' + hero.points, 10, canvas.height - 20);
}

function drawText(message, x, y) {
    ctx.fillText(message, x, y);
}

function displayMessage(message, color = 'red') {
    ctx.font = '30px Arial';
    ctx.fillStyle = color;
    ctx.textAlign = 'center';
    ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}

function displayGameScore(message, color = 'red') {
    ctx.font = '30px Arial';
    ctx.fillStyle = color;
    ctx.textAlign = 'right';
    ctx.fillText(message, canvas.width - 90, canvas.height - 30);
}

function displayLife() {
    const START_X = canvas.width - 150 - 30;
    for (let i = 0; i < game.life; i++) {
        ctx.drawImage(lifeImg, START_X + (i + 1) * 35, canvas.height - 90);
    }
}

function isHeroDead() {
    return hero.life <= 0;
}

function isEnemiesDead() {
    const enemies = gameObjects.filter((go) => go.type === 'Enemy' && !go.dead);
    return enemies.length === 0;
}

function endGame(win) {
    clearInterval(gameLoopId);

    setTimeout(() => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = 'black';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        if (win) {
            displayMessage('Victory!!! Press [Enter] to start a new game', 'green');
        } else {
            displayMessage('You died!!! Press [Enter] to start a new game');
        }
    }, 200);
}

function resetGame() {
    if (gameLoopId) {
        clearInterval(gameLoopId);
        eventEmitter.clear();
        initGame();
        gameLoopId = setInterval(() => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = 'black';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            drawPoints();
            drawLife();
            updateGameObjects();
            drawGameObjects(ctx);
        }, 100);
    }
}

function runGame() {
    gameObjects = [];
    game.life = 3;
    game.points = 0;
    game.end = false;

    createMonsters(monsterImg);
    createHero(heroImg);

    let gameLoopId = setInterval(() => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = 'black';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        displayGameScore('Score: ' + game.points);
        displayLife();
        checkGameState(gameLoopId);
        draw(ctx, gameObjects);
    }, 100);
}

window.onload = async () => {
    canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    heroImg = await loadTexture('spaceArt/png/player.png');
	heroImgLeft = await loadTexture('spaceArt/png/playerLeft.png');
	heroImgRight = await loadTexture('spaceArt/png/playerRight.png');
	heroImgDamaged = await loadTexture('spaceArt/png/playerDamaged.png');
	monsterImg = await loadTexture('spaceArt/png/enemyShip.png');
	laserRedImg = await loadTexture('spaceArt/png/laserRed.png');
	laserRedShot = await loadTexture('spaceArt/png/laserRedShot.png');
	laserGreenShot = await loadTexture('spaceArt/png/laserGreenShot.png');
	lifeImg = await loadTexture('spaceArt/png/life.png');

    game.ready = true;
    game.end = true;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    displayMessage('Press [Enter] to start', 'blue');

    // initGame();
    // let gameLoopId = setInterval(() => {
    //     ctx.clearRect(0, 0, canvas.width, canvas.height);
    //     ctx.fillStyle = "black";
    //     ctx.fillRect(0, 0, canvas.width, canvas.height);
    //     drawPoints();
    //     drawLife();
    //     updateGameObjects();
    //     drawGameObjects(ctx);
    // }, 100);

}